FrontProxy
A OOP-like abstraction for managing and manipulating graphical or any dynamic frontend instances efficiently and process them in batch. The idea is to use proxy objects as lightweight references to groups of instances (like rectangles, circles combined with other primitives) in a larger scene.
All properties of proxies are stored in linear auto-sizable buffers, which allows JIT-enabled processing and easy sync with a frontend (single transaction).
Constructor
Creates a new proxy type
FrontProxy[{properties__}, body_] _FrontProxyFunction
or if you want to specify mutable properties explicitly
FrontProxy[{properties__}, body_, {mutable__}] _FrontProxyFunction
For example to make proxy for many Disks with controllable opacity, color and position
disk = FrontProxy[{pos, c}, {
Opacity[c], RGBColor[With[{h=c}, {h, 1-h, 0.}]],
Disk[pos, 0.1]
}];
Instancing
After creating a FrontProxyFunction
you can start to create your proxies. There are two ways on how to do that
As an object
This is more user-friendly approach, you can directly use FrontProxyFunction
and provide arguments as down-values
_FrontProxyFunction [args__] _FrontProxyObject
Each argument from args
will be assigned to the defined properties__
in the same order as they appears in the constructor.
For example
disks = Table[disk[RandomReal[{-1,1}, 2], 0.2], {10}]
this line will create 10 random disks proxies.
Methods
With those object you can do following things
Show
Reveals the structure of the instance behind the proxy
Show[__FrontProxyObject] _List
It is usually used to dynamically add new objects on the graphical scene using FrontSubmit or statically place inside Graphics or Graphics3D.
It wraps the whole body into Offload to force the execution on the frontned
For example using predefined disk
structure
Show @@ disks
{FrontInstanceGroup[7808824509103943983,Offload[{Opacity[c$268241[[1]]],RGBColor[{c$268241[[1]],1-c$268241[[1]],0.`}]}]],FrontInstanceGroup[5182874663715795086,Offload[{Opacity[c$268241[[2]]],RGBColor[{c$268241[[2]],1-c$268241[[2]],0.`}]}]],
...
One can add them to the graphics scene basically by placing them directly there
Graphics[{
Show @@ disks
}, PlotRange->{{-1,1}, {-1,1}}]
or dynamically using FrontSubmit and FrontInstanceReference
scene = FrontInstanceReference[];
Graphics[{scene}, PlotRange->{{-1,1}, {-1,1}}]
FrontSubmit[Show @@ disks, scene];
Delete
Dynamically removes an FrontProxyObject
Delete[__FrontProxyObject]
For example if you placed some disks early on the scene, you can remove by directly calling at any place
Delete @@ disks
FrontProxyGet
Gets all properties as a list of the individual instance
FrontProxyGet[_FrontProxyObject] _List
The order is the same as presented in constructor.
FrontProxySet
Sets properties of the individual instance
FrontProxySet[_FrontProxyObject, values_List]
As a batch
A handy object-like representation might be slow, when it comes to creating many instances or removing them. To construct multiple instances use
FrontProxyAdd[_FrontProxyFunction, args__List] _List
as a result it returns a plain List
of integers corresponding to the internal Ids
Methods
The following methods can be applied
FrontProxyObject
Converts a pair of the internal Id
and FrontProxyFunction
to FrontProxyObject
to be used in As object
FrontProxyObject[_FrontProxyFunction, Id_Integer] _FrontProxyObject
FrontProxyShow
Reveals the structure of the instance behind the proxy
FrontProxyShow[_FrontProxyFunction, Id_Integer] _
or for a list of proxies ids
FrontProxyShow[_FrontProxyFunction, {Id__Integer}] _
FrontProxyRemove
Removes the given proxy or list of proxies by provided Ids
FrontProxyRemove[_FrontProxyFunction, Id_Integer]
FrontProxyRemove[_FrontProxyFunction, {Id__Integer}]
Common methods
There is a few general methods for _FrontProxyFunction
FrontProxyDispatch
Dispatches the changes made to the properties of proxies
FrontProxyDispatch[_FrontProxyFunction]
It is called usually when all calculations have been finished and an update is needed to see the changes. Behind the scenes it submits all buffers storing the properties of object to the frontend.
For example, after we applied changes to our proxies properties we can dispatch them to the frontend to see changes immediately
Do[
Module[{pos, c},
{pos, c} = FrontProxyGet[i];
FrontProxySet[i, {0.9 pos, c + 0.1}];
]
, {i, disks}];
FrontProxyDispatch[disk];
For the performance reasons, we recommend to avoid atomic operations like FrontProxySet/Get
and rely on buffers (see below)
FrontProxyBuffer
Provides a read access to the slice of a given property of all proxies in a form if linear packed array
FrontProxyBuffer[_FrontProxyFunction, index_Integer] _List
where index
goes from the first property provided in Constructor to the last. There is a special case
FrontProxyBuffer[_FrontProxyFunction, -1] _List
which returns a boolean array standing for the validity of the property at the given position. Since proxies are dynamic and can be created or removed any time it might temporary lead to "holes" in buffers marked as False
in the list.
Each position in buffers does correspond to Id
used by proxy in As a batch methods.
FrontProxyBufferSet
Updates a given property buffer with a new array
FrontProxyBuffer[_FrontProxyFunction, index_Integer, new_List]
It does not have an effect of FrontProxyDispatch
. You will need to call it separately after you have finished all changes with buffers.
For example one can update disks
primitives in the following way
With[{
position = FrontProxyBuffer[disk, 1]
},
With[{velocity = Map[Total] @ Table[potential[a,b], {a, position}, {b, position}]},
FrontProxyBufferSet[disk, 1, position - 0.001 velocity];
FrontProxyDispatch[disk];
];
];
Tips
If you dynamically add proxies to the scene. Call FrontProxyDispatch
before submitting it to a scene. This will make sure, that the buffer size is up-to date on the frontend as well.
For fast animations with many proxies involved turn off transition interpolation globally on Graphics using an option TransitionType
set to None
.
For processing many proxies use batch approach and pure functions with Map
, Table
or MapThread
and etc. Multiple passes using less complicated function cost less, than a single pass with one complex.
If you animate multiple properties of the same primitive to avoid unnecessary update calls on the frontend use Offload with "Static"
option, i.e.
FrontProxy[{pos, radius}, Disk[pos, Offload[radius, "Static"->True]]]
uses 1 repaint cycles, while
FrontProxy[{pos, radius}, Disk[pos, radius]]
uses 2 👎
Examples
Spherical attracting molecules
Here we will use Lennard-Jones potential to model a bunch of sphere-like molecules on 2D canvas aka Graphics
Fireworks
A crash test for the frontend system
(* Define the rectangle proxy with initial properties *)
rectangleProxy = FrontProxy[
{position, velocity, rotationAngle, lifeSpan},
Translate[
{Opacity[lifeSpan], RGBColor[With[{l = lifeSpan}, {l, 0, 1 - l}]], Rectangle[{-1, -1}, {1, 1}]},
position
]
];
(* Initialize variables *)
newProxies = {};
expiredProxies = {};
frameCounter = 1;
frameRate = 1;
lastUpdateTime = AbsoluteTime[];
sceneReference = FrontInstanceReference[];
(* Function to add new proxies at a given position *)
addProxyAtPosition[position_] := newProxies = {
newProxies,
FrontProxyAdd[
rectangleProxy,
Sequence @@ Table[
{position, RandomReal[{0.2, 1.8}] {Cos[angle], Sin[angle]} // N, RandomReal[{0, 3.14}], 1.0},
{angle, 0., 2 Pi, 2 Pi / 12.0}
]
]
};
(* Frame update logic *)
Module[{},
EventHandler["frame", Function[Null,
With[{
positions = FrontProxyBuffer[rectangleProxy, 1],
velocities = FrontProxyBuffer[rectangleProxy, 2],
lifeSpans = FrontProxyBuffer[rectangleProxy, 4],
isValid = FrontProxyBuffer[rectangleProxy, -1]
},
(* Identify expired proxies for disposal *)
expiredProxies = MapThread[
If[#1 && #2 < 0.2, #3, Nothing] &,
{isValid, lifeSpans, Range[Length[lifeSpans]]}
];
(* Update positions and life spans *)
FrontProxyBufferSet[rectangleProxy, 1, positions + velocities];
FrontProxyBufferSet[rectangleProxy, 4, lifeSpans * 0.95];
];
(* Dispatch updates to proxies *)
FrontProxyDispatch[rectangleProxy];
(* Remove expired proxies *)
If[Length[expiredProxies] > 0,
FrontProxyRemove[rectangleProxy, expiredProxies];
expiredProxies = {};
];
(* Submit new proxies *)
If[Length[newProxies] > 0,
FrontSubmit[FrontProxyShow[rectangleProxy, newProxies // Flatten], sceneReference];
newProxies = {};
];
(* Update FPS counter *)
With[{currentTime = AbsoluteTime[]},
If[currentTime - lastUpdateTime > 1.0,
frameRate = Round[(frameCounter + frameRate) / 2.0];
frameCounter = 1;
lastUpdateTime = currentTime;
,
frameCounter++;
];
];
]]
];
(* Create the graphics and event handlers *)
Graphics[
{
sceneReference,
{Directive[FontSize -> 20], Text[frameRate // Offload, {-80, -80}]},
AnimationFrameListener[frameCounter // Offload, "Event" -> "frame"],
EventHandler[
Graphics`Canvas[],
{"mousemove" -> addProxyAtPosition}
]
},
PlotRange -> {{-100, 100}, {-100, 100}},
TransitionType -> None
]